//------------------------------------------------------------------------------
// Includes files
//------------------------------------------------------------------------------

#include <sstream>
#include <iostream>
#include <fstream>
#include <Rendering/OpenGL.hpp>
#include <Rendering/Camera.hpp>
#include <Rendering/Renderer.hpp>
#include <Rendering/Texture.hpp>
#include <Rendering/Shader.hpp>
#include <Rendering/FrameBuffer.hpp>
#include <Rendering/BatchRender.hpp>
#include <Geometry/PredefinedShape.hpp>
#include <Geometry/Light.hpp>
#include <Geometry/Loader.hpp>
#include <Geometry/Object.hpp>
#include <Math/Matrix.hpp>
#include <Settings.hpp>

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// -> Define in settings & constants file
using namespace Rendering;
//------------------------------------------------------------------------------
// Forward declaration
//------------------------------------------------------------------------------
void display(void);
void reshape(int width, int height);
void keyboard(unsigned char key, int x, int y);
void special(int key, int x, int y);
void mouse(int button, int state, int x, int y);
void motion(int x, int y);

//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------
// Camera 

Camera camera;

Geometry::TLightPointer light;
Geometry::Scene scene;

Rendering::TFrameBufferPointer framebuffer;

// Last position of the mouse
int lastPositionX = RenderingSurface::Instance().surfaceWidth>>1;
int lastPositionY = RenderingSurface::Instance().surfaceHeight>>1;
//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
int main(int argc, char** argv) {

	std::string path;	
	//path = "/u/derousic/Developement/Simple/simpl3/Ressources/";
	path = "C:\\SimpleEngine\\Ressources\\";
	
	// Initialize rendering surface & renderer
 	RenderingSurface::Instance().Initialize(argc, argv, "Simpl3 engine", &display,&reshape,&keyboard,&special,&mouse,&motion);
 	
 	// Create light
 	float lightPosition[] = { 15.f, 10.f, 15.f };
	float lightDirection[] = { -1.f, -1.f, -1.f };
	light = Geometry::Light::CreateLight(lightPosition, lightDirection);
	
	// Create frame buffer
	framebuffer = Rendering::FrameBuffer::CreateFrameBuffer(DEFAULT_WIDTH,DEFAULT_HEIGHT);
	framebuffer->AddTarget(TargetType::COLOR0_TARGET,PixelFormat::PIX_R8G8B8A8);
	framebuffer->AddTarget(TargetType::DEPTH_TARGET,PixelFormat::PIX_DEPTH16);
	
	//*****************************************************************************
	Geometry::TMeshPointer meshPlaneH, meshPlaneV, rabbit;
 	// Create mesh
 	{
 		meshPlaneH = Geometry::Mesh::CreateMesh(Rendering::TRIANGLE_MESH, true); 		
		float vertex[] 	      	= { 0.f, 0.2f, 0.f,   0.f, 0.2f, 10.f,   10.f, 0.2f, 10.f,   10.f, 0.2f, 0.0f };	
		float texCoordinate[] 	= { 0.f, 0.f,   0.f, 1.f,  1.f, 1.f,   1.f, 0.f };
		float normal[] 	  		= { 0.f, 1.f, 0.f,  0.f, 1.f, 0.f,  0.f, 1.f, 0.f,  0.f, 1.f, 0.f };	
		unsigned int index[] 	= { 0,2,3, 0,1,2 };
		Geometry::TIBufferPointer vertexBuffer 	= Geometry::IBuffer::CreateBuffer(VERTEX_BUFFER,4);	
		Geometry::TIBufferPointer texBuffer 	= Geometry::IBuffer::CreateBuffer(TEXCOORD0_BUFFER,4);
		Geometry::TIBufferPointer normalBuffer 	= Geometry::IBuffer::CreateBuffer(NORMAL_BUFFER,4);
		Geometry::TIBufferPointer indexBuffer 	= Geometry::IBuffer::CreateBuffer(INDEX_BUFFER, 6);	
		vertexBuffer->Load(vertex);	
		texBuffer->Load(texCoordinate);
		normalBuffer->Load(normal);		
		indexBuffer->Load(index);

		meshPlaneH->AddBuffer(vertexBuffer);
		meshPlaneH->AddBuffer(texBuffer);
		meshPlaneH->AddBuffer(normalBuffer);
		meshPlaneH->AddBuffer(indexBuffer);
	}
	
	// Create mesh
	{
 		meshPlaneV = Geometry::Mesh::CreateMesh(Rendering::TRIANGLE_MESH, true);
		float vertex2[] 	   	= { 8.f, 0.2f, 6.f,   8.f, 2.f, 6.f,   6.f, 2.0f, 8.0f,  6.f, 0.2f, 8.0f };	
		float texCoordinate2[] 	= { 0.f, 0.f,   0.f, 1.f,  1.f, 1.f,   1.f, 0.f };
		float normal2[] 		= { 1.f, 0.f, 1.f,  1.f, 0.f, 1.f,  1.f, 0.f, 1.f,  1.f, 0.f, 1.f };
		unsigned int index2[] 	= { 0,2,3, 0,1,2 };
		Geometry::TIBufferPointer vertexBuffer2 = Geometry::IBuffer::CreateBuffer(VERTEX_BUFFER,4);	
		Geometry::TIBufferPointer texBuffer2 	= Geometry::IBuffer::CreateBuffer(TEXCOORD0_BUFFER,4);
		Geometry::TIBufferPointer normalBuffer2 = Geometry::IBuffer::CreateBuffer(NORMAL_BUFFER,4);
		Geometry::TIBufferPointer indexBuffer2 	= Geometry::IBuffer::CreateBuffer(INDEX_BUFFER, 6);
		vertexBuffer2->Load(vertex2);
		texBuffer2->Load(texCoordinate2);
		normalBuffer2->Load(normal2);		
		indexBuffer2->Load(index2);

		meshPlaneV->AddBuffer(vertexBuffer2);
		meshPlaneV->AddBuffer(texBuffer2);
		meshPlaneV->AddBuffer(normalBuffer2);
		meshPlaneV->AddBuffer(indexBuffer2);
	}
	
	
	{
		std::string file = path;
		file += "bun_zipper_res2.ply";		
		Geometry::PLYLoader loader;
		rabbit = loader.LoadFromFile(file);		
	}
	//*****************************************************************************
	Rendering::TShaderProgramPointer simpleSh, textureSh, shadowSh, lightingSh;
	// Create shader
	{
		std::string vertexFile, fragmentFile;
		vertexFile += path;
		fragmentFile += path;		
		vertexFile += "Simple.vs";
		fragmentFile += "Simple.fs";
		simpleSh = ShaderProgram::CreateShader(vertexFile,fragmentFile);
	}
	{
		std::string vertexFile, fragmentFile;
		vertexFile += path;
		fragmentFile += path;
		vertexFile += "Texture.vs";
		fragmentFile += "Texture.fs";
		textureSh = ShaderProgram::CreateShader(vertexFile,fragmentFile);
	}	
	{
		std::string vertexFile, fragmentFile;
		vertexFile += path;
		fragmentFile += path;		
		vertexFile += "Shadow.vs";
		fragmentFile += "Shadow.fs";
		shadowSh = ShaderProgram::CreateShader(vertexFile,fragmentFile);
	}

	{
		std::string vertexFile, fragmentFile;
		vertexFile += path;
		fragmentFile += path;		
		vertexFile += "PerPixelLighting.vs";
		fragmentFile += "PerPixelLighting.fs";
		lightingSh = ShaderProgram::CreateShader(vertexFile,fragmentFile);
	}
	//*****************************************************************************
	Geometry::TObjectPointer objectPlaneH, objectPlaneV, objectPlaneHVisu, objectRabbit;
	{
		Matrix::Matrix4 translation;
		Matrix::Translate4(translation, -5.f, 0.f, -5.f);
		objectPlaneH = Geometry::Object::CreateObject(translation,meshPlaneH);
	}
	
	{
		Matrix::Matrix4 translation;
		Matrix::Translate4(translation, 0, 2, 0);
		objectPlaneV = Geometry::Object::CreateObject(translation, meshPlaneV);
		//objectPlaneV = Geometry::Object::CreateObject(Matrix::Identity,meshPlaneV);	
	}
	
	{
		Matrix::Matrix4 translation;
		Matrix::Translate4(translation, 15, 0, 0);
		objectPlaneHVisu = Geometry::Object::CreateObject(translation, meshPlaneH);
		//objectPlaneHVisu = Geometry::Object::CreateObject(Matrix::Identity,meshPlaneH);	
	}
	
	{
		Matrix::Matrix4 translation;
		Matrix::Translate4(translation, 0, 0, 0);
		objectRabbit = Geometry::Object::CreateObject(translation, rabbit);		
	}
	
	//*****************************************************************************
	// Create environnment
	{
		RessourceContext* publicContext;
		RessourceContext* privateContext;		
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
				
		objectPlaneH->AddPass(Rendering::VIEW_PASS, simpleSh, TIShaderContextPointer(publicContext),TIShaderContextPointer(privateContext));		
	}
	
	{	
		RessourceContext* publicContext;
		RessourceContext* privateContext;		
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
				
		objectPlaneV->AddPass(Rendering::VIEW_PASS, simpleSh, TIShaderContextPointer(publicContext), TIShaderContextPointer(privateContext));
	}
	
	{	
		RessourceContext* publicContext;
		RessourceContext* privateContext;		
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
				
		objectPlaneHVisu->AddPass(Rendering::VIEW_PASS, simpleSh, TIShaderContextPointer(publicContext), TIShaderContextPointer(privateContext));
	}
	
	{	
		RessourceContext* publicContext;
		RessourceContext* privateContext;		
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
				
		objectRabbit->AddPass(Rendering::VIEW_PASS, simpleSh, TIShaderContextPointer(publicContext), TIShaderContextPointer(privateContext));
	}
	
	{	
		RessourceContext* publicContext;
		RessourceContext* privateContext;		
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
		
		TITexturePointer texture = framebuffer->GetTarget(TargetType::DEPTH_TARGET);
		privateContext->AddRessource<ITexture>("currentTexture",texture);
				
		objectPlaneHVisu->AddPass(Rendering::SHADOW_PASS, textureSh, TIShaderContextPointer(publicContext), TIShaderContextPointer(privateContext));
	}
	
	{
		RessourceContext* publicContext;
		RessourceContext* privateContext;
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
		
		TITexturePointer texture = framebuffer->GetTarget(TargetType::DEPTH_TARGET);
		privateContext->AddRessource<ITexture>("depthTexture",texture);
		privateContext->AddRessource<Matrix::Matrix4>("lightViewMatrix", light->GetViewMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("lightProjectionMatrix", light->GetProjectionMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("cameraViewInverseMatrix",camera.matInverse);
		
//		privateContext->AddRessource<Vector::Vector3>("lightPosition",light->GetPosition());
//		privateContext->AddRessource<Vector::Vector3>("lightDirection",light->GetDirection());
		
		objectPlaneH->AddPass(Rendering::SHADOW_PASS,shadowSh,TIShaderContextPointer(publicContext),TIShaderContextPointer(privateContext));
	}
	
	{
		RessourceContext* publicContext;
		RessourceContext* privateContext;
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
		
		TITexturePointer texture = framebuffer->GetTarget(TargetType::DEPTH_TARGET);
		privateContext->AddRessource<ITexture>("depthTexture",texture);
		privateContext->AddRessource<Matrix::Matrix4>("lightViewMatrix",light->GetViewMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("lightProjectionMatrix",light->GetProjectionMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("cameraViewInverseMatrix",camera.matInverse);		
		
//		privateContext->AddRessource<Vector::Vector3>("lightPosition",light->GetPosition());
//		privateContext->AddRessource<Vector::Vector3>("lightDirection",light->GetDirection());
		
		objectPlaneV->AddPass(Rendering::SHADOW_PASS,shadowSh,TIShaderContextPointer(publicContext),TIShaderContextPointer(privateContext));
	}
	
	{
		RessourceContext* publicContext;
		RessourceContext* privateContext;
		publicContext   = new RessourceContext();	
		privateContext  = new RessourceContext();
		
		TITexturePointer texture = framebuffer->GetTarget(TargetType::DEPTH_TARGET);
		privateContext->AddRessource<ITexture>("depthTexture",texture);
		privateContext->AddRessource<Matrix::Matrix4>("lightViewMatrix",light->GetViewMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("lightProjectionMatrix",light->GetProjectionMatrix());
		privateContext->AddRessource<Matrix::Matrix4>("cameraViewInverseMatrix",camera.matInverse);

//		privateContext->AddRessource<Vector::Vector3>("lightPosition",light->GetPosition());
//		privateContext->AddRessource<Vector::Vector3>("lightDirection",light->GetDirection());
				
		objectRabbit->AddPass(Rendering::SHADOW_PASS,shadowSh,TIShaderContextPointer(publicContext),TIShaderContextPointer(privateContext));
	}
	
	// Add object to scene
	scene.AddObject(objectPlaneH);
	scene.AddObject(objectPlaneHVisu);
	scene.AddObject(objectPlaneV);
	scene.AddObject(objectRabbit);
	
	// Launch rendering thread
	Renderer::Instance().Run();
	return 0;
}

//-----------------------------------------------------------------------------
// Keyboard
//-----------------------------------------------------------------------------
template<typename T>
bool fromString( const std::string & Str, T & Dest )
{
    // cr�er un flux � partir de la cha�ne donn�e
    std::istringstream iss( Str );
    // tenter la conversion vers Dest
    return iss >> Dest != 0;
}
//-----------------------------------------------------------------------------
void keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
		case 'w' :
			camera.MoveDeeply( MOVING_INCREMENT );
			break;
		case 's' :
			camera.MoveDeeply( -MOVING_INCREMENT);
			break;
		case 'a' :
			camera.MoveHorizontaly( -MOVING_INCREMENT);
			break;
		case 'd' :
			camera.MoveHorizontaly( MOVING_INCREMENT);
			break;
		case 'n' :
			camera.MoveVertical( -MOVING_INCREMENT);
			break;
		case 'u' :
			camera.MoveVertical( MOVING_INCREMENT);
			break;
		case 'j' :
			camera.RotateCamera( 1.0f, 0.0f );
			break;
		case 'k' :
			camera.RotateCamera( -1.0f,0.0f);
			break;
		case 27 :			
			RenderingSurface::Instance().Finalize();
			exit(EXIT_SUCCESS);
			break;
	}
}
//-----------------------------------------------------------------------------
void special(int key, int x, int y) {
	switch (key)
	{
		case GLUT_KEY_PAGE_UP :			
			break;
		case GLUT_KEY_PAGE_DOWN :			
			break;
		case GLUT_KEY_LEFT :
			break;
		case GLUT_KEY_RIGHT :
			break;
		case GLUT_KEY_UP :
			break;
		case GLUT_KEY_DOWN :
			break;
	}
}
//-----------------------------------------------------------------------------
// Mouse
//-----------------------------------------------------------------------------
void mouse(int button, int state, int x, int y) {
	//specialKey = glutGetModifiers();
	if (button == GLUT_LEFT_BUTTON) {		
	}
}
//------------------------------------------------------------------------------
void motion(int x, int y) {
	// Compute camera rotation angle
	float angleX, angleY;
	angleX = x - (RenderingSurface::Instance().surfaceWidth>>1);
	angleY = y - (RenderingSurface::Instance().surfaceHeight>>1);
	if ( !( abs(lastPositionX-x)<1 && abs(lastPositionY-y)<1 )) {	
		// Rotate Camera
		float angleX, angleY;
		angleX = x - (RenderingSurface::Instance().surfaceWidth>>1);
		angleY = y - (RenderingSurface::Instance().surfaceHeight>>1);
		angleX /= 10.f;
		angleY /= 10.f;
		camera.RotateCamera(-angleX,angleY);
		// Reset mouse position
		RenderingSurface::Instance().WarpMousePosition( 
			RenderingSurface::Instance().surfaceWidth>>1,
			RenderingSurface::Instance().surfaceHeight>>1);
		// Update last position		
		lastPositionX = RenderingSurface::Instance().surfaceWidth>>1;
		lastPositionY = RenderingSurface::Instance().surfaceHeight>>1;
	}
}

//-----------------------------------------------------------------------------
// Reshape
//-----------------------------------------------------------------------------
void reshape(int width, int height)
{
	RenderingSurface::Instance().SetSize(width, height);
}
//-----------------------------------------------------------------------------
// Display rendered image
//-----------------------------------------------------------------------------
void display() {
	// Update frame rate	
	FPSCounter::Update();
	// Light pass
	{
		framebuffer->Enable();		
		glEnable(GL_POLYGON_OFFSET_FILL);
		glPolygonOffset(1.0f, 1.0f);
		Renderer::Instance().StartRender();
		Renderer::Instance().SetPointOfView( &(*light) );		
		//Renderer::Instance().SetPointOfView( &camera );
		RenderingQueue queue;
		scene.Render(Rendering::VIEW_PASS, queue);
		queue.RenderQueue();
		Renderer::Instance().Draw();
		glDisable(GL_POLYGON_OFFSET_FILL);		
		framebuffer->Disable();
	}
	
	// Camera Pass
	{		
		Renderer::Instance().StartRender();
		Renderer::Instance().SetPointOfView( &camera );
		RenderingQueue queue;
		scene.Render(Rendering::SHADOW_PASS, queue);		
		queue.RenderQueue();
		Renderer::Instance().Draw();
	}

	static int cpt = 0;
	static float alpha = 0;
	if((cpt%50) == 0) {	
		alpha += 2*3.14/100;
		float radius = 15.f;
		Vector::Vector3 position  = {radius*cos(alpha), 5.f, radius*sin(alpha)};
		Vector::Vector3 direction = { -radius*cos(alpha), -5.f, -radius*sin(alpha)};
		Vector::Norm3(direction,direction);
		Vector::Copy3(light->GetPosition(),position);	
		Vector::Copy3(light->GetDirection(),direction);
	}
	++cpt;
	if(cpt==1000)
		cpt =0;	
			
	// Overlay pass
	Renderer::Instance().SetProjection(Renderer::ORTHOGONAL, camera);
	Renderer::Instance().SetPointOfView(Matrix::Identity);
	std::stringstream fps;
	fps << FPSCounter::GetFrameRate();
	Renderer::Instance().Draw(20, 20, fps.str());
	
	// Swap buffer
	Renderer::Instance().EndRender();	
}
